using System;
using System.Diagnostics;
using System.ServiceProcess;
using System.Timers;
using System.Xml;
using System.Xml.Serialization;
using System.Configuration;
using NLog;

namespace Koinespa.ProcessHost
{
    /// <summary>
    /// Struct used to keep process instance info together with configuration
    /// </summary>
    struct ProcessDescription
    {
        public ProcessType Config;
        public Process Image;
    }

    public partial class ProcessHostSvc : ServiceBase
    {
        #region Consts

        public const string CFG_FILE = "ProcessesFile";
        public const string EVENTLOG_SRC = "ProcessHostSvc";
        public const string MSG_START_ERROR = "Error occured during starting processes. /r/n{0}";
        public const string MSG_STARTING = "Starting process {0} with arguments {1}";
        public const string MSG_STARTED = "Process {0} has been started with PID {1}";
        public const string MSG_RESTARTED = "Process {0} has been restarted with PID {1}";
        public const string MSG_STOPPING = "Stopping process {0}";
        public const string MSG_STOPED = "Process {0} has been stopped";
        public const string MSG_TERMINATED = "Process {0} has been already terminated";
        public const string MSG_STOP_ERROR = "Error occured during stopping processes. /r/n{0}";
        public const string MSG_UNHANDLED_ERROR = "Unhandled exception occured in Process Host Service. /r/nException: {0}/r/nProcess Host Service is terminating: {1}";
        public const string MSG_CONFIG_ERROR = "Error occured during loading configuration in Process Host Service ({0}). /r/nException: {1}";
    
        #endregion Consts

        private static readonly Logger logger = LogManager.GetCurrentClassLogger();

        private Processes processCfg;               // deserialized configuration
        private ProcessDescription[] processDesc;   // processes' configurations and images

        private Timer tmr;

        public ProcessHostSvc()
        {
            InitializeComponent();

            tmr = new Timer(60000);
            tmr.Elapsed += tmr_Elapsed;

            // register handler for unhandled exceptions
            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

            // read configuration
            ReadConfig();
        }

        private void tmr_Elapsed(object sender, ElapsedEventArgs e)
        {
            for (int i = 0; i < processDesc.Length; i++)
            {
                ProcessDescription d = processDesc[i];
                if (d.Image == null) continue;

                if (d.Image.HasExited && d.Config.AutoRestart)
                {
                    int wt = d.Config.WaitBeforeRestart;
                    if (wt < 0) wt = 0;
                    if (d.Image.ExitTime.AddMinutes(wt) < DateTime.Now)
                    {
                        // Riavvia il processo
                        ProcessStartInfo psi = new ProcessStartInfo
                        {
                            Arguments = d.Config.Arguments,
                            FileName = d.Config.FileName,
                            WorkingDirectory = d.Config.WorkingDirectory
                        };

                        processDesc[i].Image = Process.Start(psi);
                        if (d.Config.LowPriority)
                            processDesc[i].Image.PriorityClass = ProcessPriorityClass.BelowNormal;

                        logger.Warn(string.Format(MSG_RESTARTED, processDesc[i].Image.MainModule.ModuleName,
                                                  processDesc[i].Image.Id));
                    }
                }
            }
        }

        void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            logger.Error(string.Format(MSG_UNHANDLED_ERROR, e.ExceptionObject, e.IsTerminating));
        }


        /// <summary>
        /// Deserializes configuration into processesCfg object
        /// </summary>
        protected void ReadConfig()
        {
            string filename = ConfigurationManager.AppSettings[CFG_FILE];

            try
            {
                XmlSerializer xSerializer = new XmlSerializer(typeof(Processes));
                using (XmlTextReader xReader = new XmlTextReader(filename))
                {
                    processCfg = (Processes)xSerializer.Deserialize(xReader);
                }
                processDesc = new ProcessDescription[processCfg.ProcessInfo.Length];
            }
            catch (Exception ex)
            {
                processDesc = null;
                processCfg = null;

                logger.Error(string.Format(MSG_CONFIG_ERROR, filename, ex));
            }
        }

        /// <summary>
        /// Starts all configured processes
        /// </summary>
        /// <param name="args"></param>
        protected override void OnStart(string[] args)
        {
            if (processCfg != null)
            {
                int descIndex = 0;
                foreach(ProcessType proc in processCfg.ProcessInfo)
                {
                    try
                    {
                        logger.Info(string.Format(MSG_STARTING, proc.FileName, proc.Arguments));

                        // set process properties using configuration
                        ProcessStartInfo psi = new ProcessStartInfo
                                                   {
                                                       Arguments = proc.Arguments,
                                                       FileName = proc.FileName,
                                                       WorkingDirectory = proc.WorkingDirectory
                                                   };

                        // start process and save it in processDesc
                        processDesc[descIndex].Config = proc;
                        processDesc[descIndex].Image = Process.Start(psi);
                        if (proc.LowPriority)
                            processDesc[descIndex].Image.PriorityClass = ProcessPriorityClass.BelowNormal;

                        logger.Info(string.Format(MSG_STARTED, processDesc[descIndex].Image.MainModule.ModuleName, processDesc[descIndex].Image.Id));

                        descIndex++;
                    }
                    catch (Exception ex)
                    {
                        logger.Error(ex.ToString());
                    }
                }

                tmr.Start();
            }
        }

        /// <summary>
        /// Stops processes
        /// </summary>
        protected override void OnStop()
        {
            tmr.Stop();
            if (processDesc != null)
            {
                for (int i = 0; i < processDesc.Length; i++)
                {
                    try
                    {
                        if (processDesc[i].Image.HasExited)
                        { // this process has already ended -> write info in event log
                            logger.Info(string.Format(MSG_TERMINATED, processDesc[i].Config.FileName));

                        }
                        else
                        { // kill process
                            logger.Info(string.Format(MSG_STOPPING, processDesc[i].Config.FileName));

                            processDesc[i].Image.Kill();
                            processDesc[i].Image.WaitForExit();

                            logger.Info(string.Format(MSG_STOPED, processDesc[i].Config.FileName));
                        }
                    }
                    catch (Exception ex)
                    {
                        logger.Error(string.Format(MSG_STOP_ERROR, ex));
                    }
                }
            }
        }

        

    
    }
}